#pragma once

#include <mcp/config.h>
#include <mcp/forward.h>
#include <mcp/types.h>
#include <mcp/error.h>

#include <memory>
#include <string>
#include <vector>
#include <optional>
#include <functional>
#include <coroutine>

namespace mcp {
namespace client {

/// @brief Forward declarations
class Transport;

/// @brief Client configuration
struct ClientConfig {
    std::string name = "MCP Client";
    std::string version = "1.0.0";
    int timeout_seconds = 30;
    bool enable_compression = true;
    size_t max_message_size = 1024 * 1024; // 1MB
    bool auto_reconnect = true;
    int reconnect_interval_ms = 5000;
    int max_reconnect_attempts = 10;
};

/// @brief Client capabilities
struct ClientCapabilities {
    bool tools = true;
    bool resources = true;
    bool prompts = true;
    bool logging = true;
    bool progress = true;
    bool sampling = true;
    bool elicitation = true;
};

/// @brief MCP Client for connecting to MCP servers
class Client : public std::enable_shared_from_this<Client> {
public:
    explicit Client(std::unique_ptr<Transport> transport,
                   const ClientConfig& config = {});
    ~Client();

    /// @brief Connect to server
    /// @return true if connected successfully, false otherwise
    bool connect();

    /// @brief Disconnect from server
    void disconnect();

    /// @brief Check if connected to server
    /// @return true if connected, false otherwise
    bool is_connected() const;

    /// @brief Get server information
    /// @return Server information or error
    Result<json::Value> get_server_info();

    /// @brief Get server capabilities
    /// @return Server capabilities or error
    Result<json::Value> get_capabilities();

    /// @brief Get server protocol version
    /// @return Protocol version or error
    Result<std::string> get_protocol_version();

    /// @brief List available tools
    /// @return List of tools or error
    Result<std::vector<types::Tool>> list_tools();

    /// @brief Call a tool
    /// @param name Tool name
    /// @param arguments Tool arguments
    /// @return Tool result or error
    Result<json::Value> call_tool(const std::string& name, const json::Value& arguments);

    /// @brief List available resources
    /// @return List of resources or error
    Result<std::vector<types::Resource>> list_resources();

    /// @brief List available resource templates
    /// @return List of resource templates or error
    Result<std::vector<types::ResourceTemplate>> list_resource_templates();

    /// @brief Read a resource
    /// @param uri Resource URI
    /// @return Resource content or error
    Result<types::ResourceContent> read_resource(const std::string& uri);

    /// @brief List available prompts
    /// @return List of prompts or error
    Result<std::vector<types::Prompt>> list_prompts();

    /// @brief Call a prompt
    /// @param name Prompt name
    /// @param arguments Prompt arguments
    /// @return Prompt messages or error
    Result<std::vector<types::PromptMessage>> call_prompt(const std::string& name,
                                                             const json::Value& arguments);

    /// @brief Send a logging message
    /// @param level Log level
    /// @param message Log message
    /// @param logger_name Optional logger name
    /// @return true if sent successfully, false otherwise
    bool send_log(types::LogLevel level, const std::string& message,
                  const std::optional<std::string>& logger_name = {});

    /// @brief Send a progress notification
    /// @param token Progress token
    /// @param progress Progress value (0.0 to 1.0)
    /// @param total Optional total value
    /// @param message Optional progress message
    /// @return true if sent successfully, false otherwise
    bool send_progress(const types::ProgressToken& token, double progress,
                       std::optional<double> total = {},
                       std::optional<std::string> message = {});

    /// @brief Request user input with schema
    /// @param message Message to show user
    /// @param schema Schema for expected input
    /// @return User input or error
    template<typename SchemaT>
    Result<json::Value> elicit(const std::string& message, const SchemaT& schema);

    /// @brief Request LLM sampling
    /// @param messages Message history
    /// @param options Sampling options
    /// @return Sampling result or error
    Result<types::SamplingResult> create_message(
        const std::vector<types::SamplingMessage>& messages,
        const types::SamplingOptions& options = {});

    /// @brief Set client capabilities
    /// @param capabilities Client capabilities
    void set_capabilities(const ClientCapabilities& capabilities);

    /// @brief Get client capabilities
    /// @return Client capabilities
    const ClientCapabilities& get_capabilities() const;

    /// @brief Set client configuration
    /// @param config Client configuration
    void set_config(const ClientConfig& config);

    /// @brief Get client configuration
    /// @return Client configuration
    const ClientConfig& get_config() const;

    /// @brief Set message handler for server notifications
    /// @param handler Function to handle notifications
    void set_notification_handler(std::function<void(const std::string&, const json::Value&)> handler);

    /// @brief Set error handler
    /// @param handler Function to handle errors
    void set_error_handler(std::function<void(const Error&)> handler);

private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};

// Template implementation for elicit method
template<typename SchemaT>
Result<json::Value> Client::elicit(const std::string& message, const SchemaT& schema) {
    // This is a simplified implementation - in a real implementation,
    // you'd convert the SchemaT to a proper JSON schema
    auto params = json::Value::object({
        {"message", message},
        {"schema", json::Value::object({{"type", "object"}})}
    });

    auto request = json::Value::object({
        {"method", "elicitation/request"},
        {"params", std::move(params)}
    });

    // Send request and wait for response
    // This would be implemented with proper async/await pattern
    return Error::unsupported_operation("Elicitation not implemented in this client");
}

/// @brief Factory function to create client with stdio transport
std::shared_ptr<Client> create_stdio_client(const ClientConfig& config = {});

/// @brief Factory function to create client with HTTP transport
std::shared_ptr<Client> create_http_client(const std::string& url,
                                           const ClientConfig& config = {});

/// @brief Factory function to create client with WebSocket transport
std::shared_ptr<Client> create_websocket_client(const std::string& url,
                                                const ClientConfig& config = {});

/// @brief Factory function to create client with SSE transport
std::shared_ptr<Client> create_sse_client(const std::string& url,
                                         const ClientConfig& config = {});

} // namespace client
} // namespace mcp